package com.such.kit.io.simple.support;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.tools.ant.filters.StringInputStream;
import com.such.kit.file.FileCommons;
import com.such.kit.io.source.DatabaseIOSource;
import com.such.kit.io.source.MemoryIOSource;
import com.such.kit.io.support.IOSource;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * 简单快速使用的 IO 工具抽象类
 * 设计初衷在于可方便的对数据源中的数据进行导入、导出
 * 
 * 通过定义 {@link IOSource} 可以指定不同数据源，如：{@link DatabaseIOSource}、{@link MemoryIOSource}
 * 通过继承、重写 input、output 方法，将数据导入到 {@link IOSource} 或者从 {@link IOSource} 中导出数据
 * </pre>
 */
public abstract class IOSimple {

	/** charset：[默认值为 {@link StringCommons#CHARSET_GBK}] [参考 {@link StringCommons}.ENCODE_*] */
	protected String charset = StringCommons.CHARSET_GBK;
	/**
	 * <pre>
	 * args：自定义参数集
	 * 
	 * 通过自定义参数可以引入外部变量供程序中使用，并可通过 {@link PlaceholderCommons 占位符} 在一些特殊上下文中引用键值
	 * 不同的 {@link IOSource} 实现对 args 的使用方式具体参考各实现的说明
	 * </pre>
	 */
	protected Map<String, Object> args = new HashMap<String, Object>();

	/**
	 * <pre>
	 * 设置字符集
	 * </pre>
	 * @param charset [参考 {@link StringCommons}.ENCODE_*]
	 */
	public void setCharset(String charset) {
		this.charset = charset;
	}

	/**
	 * <pre>
	 * 设置 {@link #args 自定义参数}
	 * </pre>
	 * @param key 参数键
	 * @param value 参数值
	 */
	public void putArg(String key, Object value) {
		this.args.put(key, value);
	}

	/**
	 * <pre>
	 * 设置自定义参数集
	 * </pre>
	 * @param args 参数集 [参考 {@link #args}]
	 */
	public void putArgs(Map<String, Object> args) {
		this.args.putAll(args);
	}

	/**
	 * <pre>
	 * 清空自定义参数集
	 * </pre>
	 */
	public void clearArgs() {
		this.args.clear();
	}

	/**
	 * <pre>
	 * 获取数据文件类型
	 * 
	 * 文件类型 [举例]
	 *   xxx.xml > xml
	 *   xxx.txt > txt
	 *   xxx.xls > txt
	 *   xxx.xlsx > xlsx
	 *   xxx.json > json
	 * </pre>
	 * @return 文件类型
	 */
	public abstract String getFileType();

	/**
	 * <pre>
	 * 导入指定数据文件
	 * </pre>
	 * @param ioSource 数据源
	 * @param resourcePath 数据文件路径
	 * @throws Exception
	 */
	public void input(IOSource ioSource, String resourcePath) throws Exception {
		input(ioSource, new FileInputStream(resourcePath));
	}

	/**
	 * <pre>
	 * 导入数据字符串
	 * 
	 * [警告] 并不是所有类型的文件都可以通过字符串方式输入，如 excel 文件，可以根据实际情况进行 @deprecated 
	 * </pre>
	 * @param ioSource 数据源
	 * @param resourceString 数据字符串
	 * @param charset 字符集 [可空] [参考 {@link StringCommons}.ENCODE_*]
	 * @throws Exception
	 */
	public void input(IOSource ioSource, String resourceString, String charset) throws Exception {
		InputStream resourceIn = null;
		if (ValidateCommons.isEmpty(charset)) {
			resourceIn = new StringInputStream(resourceString);
		} else {
			resourceIn = new StringInputStream(resourceString, charset);
		}
		input(ioSource, resourceIn);
	}

	/**
	 * <pre>
	 * 导入数据 ByteArray
	 * </pre>
	 * @param ioSource 数据源
	 * @param resourceBytes 数据 ByteArray
	 * @throws Exception
	 */
	public void input(IOSource ioSource, byte[] resourceBytes) throws Exception {
		input(ioSource, new ByteArrayInputStream(resourceBytes));
	}

	/**
	 * <pre>
	 * 从输入流获取数据导入
	 * 此方法需要进行重写实现，理论上来说所有类型的文件、数据都可以通过 InputStream 接入，这是个高度抽象的方法
	 * 
	 * 其它 input 方法最终都会调用到此方法
	 * </pre>
	 * @param ioSource 数据源
	 * @param resourceIn 数据输入流
	 * @throws Exception
	 */
	public abstract void input(IOSource ioSource, InputStream resourceIn) throws Exception;

	/**
	 * <pre>
	 * 将导出的数据直接按 {@link #charset 编码} 读取为字符串返回
	 * 
	 * [警告] 并不是所有类型的文都可以通过字符串方式输入，如 excel 文件，可以根据实际情况进行 @deprecated 
	 * </pre>
	 * @param ioSource 数据源
	 * @return 数据字符串
	 * @throws Exception
	 */
	public String output(IOSource ioSource) throws Exception {
		return StreamCommons.textReader(new InputStreamReader(new FileInputStream(output(ioSource, "", "")), this.charset));
	}

	/**
	 * <pre>
	 * 将导出的数据生成文件存放在指定目录下，并返回导出文件的绝对路径
	 * 
	 * 当 path、name 为空时，会在临时目录下生成一个 {@link UUID} 文件名的临时文件
	 * </pre>
	 * @param ioSource 数据源
	 * @param path 目录 [可空] [默认值为 java.io.tmpdir]
	 * @param name 文件名 [可空] [默认值为 {@link UUID}]
	 * @return 导出文件的绝对路径
	 * @throws Exception
	 */
	public String output(IOSource ioSource, String path, String name) throws Exception {
		if (ValidateCommons.isEmpty(path)) {
			path = StringCommons.merger(System.getProperty("java.io.tmpdir"), "IO_DIR");  // 默认放在临时目录下
			path = FileCommons.changeCompatSeparator(path);
			FileCommons.createFolder(path);
		} else {
			path = FileCommons.changeCompatSeparator(path);
		}
		if (ValidateCommons.isEmpty(name)) {
			name = UUID.randomUUID().toString();
		}
		String filePath = FileCommons.appendPathNode(path, StringCommons.merger(name, ".", getFileType()));
		FileOutputStream out = new FileOutputStream(filePath);
		try {
			output(ioSource, out);
		} finally {
			StreamCommons.close(out);
		}
		return filePath;
	}

	/**
	 * <pre>
	 * 将数据从输出流导出
	 * 此方法需要进行重写实现，理论上来说所有类型的文件、数据都可以通过 OutputStream 输出，这是个高度抽象的方法
	 * 
	 * 其它 output 方法最终都会调用到此方法
	 * </pre>
	 * @param ioSource 数据源
	 * @param resourceOut 数据输出流
	 * @throws Exception
	 */
	public abstract void output(IOSource ioSource, OutputStream resourceOut) throws Exception;

}
